package util;

import java.util.ArrayList;
import java.util.List;

/**
 * Represents the geometry of a cell. For vertices, the geometry consists of the
 * x- and y-location, as well as the width and height. For edges, the geometry
 * either defines the source- and target-terminal, or it defines the respective
 * terminal points.
 * 
 * For edges, if the geometry is relative (default), then the x-coordinate is
 * used to describe the distance from the center of the edge from -1 to 1 with 0
 * being the center of the edge and the default value, and the y-coordinate is
 * used to describe the absolute, orthogonal distance in pixels from that point.
 * In addition, the offset is used as an absolute offset vector from the
 * resulting point.
 */
public class astGeometry extends astRectangle {

	/**
	 * 
	 */
	private static final long serialVersionUID = 2649828026610336589L;

	/**
	 * Global switch to translate the points in translate. Default is true.
	 */
	public static transient boolean TRANSLATE_CONTROL_POINTS = true;

	/**
	 * Stores alternate values for x, y, width and height in a rectangle.
	 * Default is null.
	 */
	protected astRectangle alternateBounds;

	/**
	 * Defines the source- and target-point of the edge. This is used if the
	 * corresponding edge does not have a source vertex. Otherwise it is
	 * ignored. Default is null.
	 */
	protected astPoint sourcePoint, targetPoint;

	/**
	 * List of mxPoints which specifies the control points along the edge. These
	 * points are the intermediate points on the edge, for the endpoints use
	 * targetPoint and sourcePoint or set the terminals of the edge to a
	 * non-null value. Default is null.
	 */
	protected List<astPoint> points;

	/**
	 * Holds the offset of the label for edges. This is the absolute vector
	 * between the center of the edge and the top, left point of the label.
	 * Default is null.
	 */
	protected astPoint offset;

	/**
	 * Specifies if the coordinates in the geometry are to be interpreted as
	 * relative coordinates. Default is false. This is used to mark a geometry
	 * with an x- and y-coordinate that is used to describe an edge label
	 * position, or a relative location with respect to a parent cell's width
	 * and height.
	 */
	protected boolean relative = false;

	/**
	 * Constructs a new geometry at (0, 0) with the width and height set to 0.
	 */
	public astGeometry() {
		this(0, 0, 0, 0);
	}

	/**
	 * Constructs a geometry using the given parameters.
	 * 
	 * @param x
	 *            X-coordinate of the new geometry.
	 * @param y
	 *            Y-coordinate of the new geometry.
	 * @param width
	 *            Width of the new geometry.
	 * @param height
	 *            Height of the new geometry.
	 */
	public astGeometry(double x, double y, double width, double height) {
		super(x, y, width, height);
	}

	/**
	 * Returns the alternate bounds.
	 */
	public astRectangle getAlternateBounds() {
		return alternateBounds;
	}

	/**
	 * Sets the alternate bounds to the given rectangle.
	 * 
	 * @param rect
	 *            Rectangle to be used for the alternate bounds.
	 */
	public void setAlternateBounds(astRectangle rect) {
		alternateBounds = rect;
	}

	/**
	 * Returns the source point.
	 * 
	 * @return Returns the source point.
	 */
	public astPoint getSourcePoint() {
		return sourcePoint;
	}

	/**
	 * Sets the source point.
	 * 
	 * @param sourcePoint
	 *            Source point to be used.
	 */
	public void setSourcePoint(astPoint sourcePoint) {
		this.sourcePoint = sourcePoint;
	}

	/**
	 * Returns the target point.
	 * 
	 * @return Returns the target point.
	 */
	public astPoint getTargetPoint() {
		return targetPoint;
	}

	/**
	 * Sets the target point.
	 * 
	 * @param targetPoint
	 *            Target point to be used.
	 */
	public void setTargetPoint(astPoint targetPoint) {
		this.targetPoint = targetPoint;
	}

	/**
	 * Returns the list of control points.
	 */
	public List<astPoint> getPoints() {
		return points;
	}

	/**
	 * Sets the list of control points to the given list.
	 * 
	 * @param value
	 *            List that contains the new control points.
	 */
	public void setPoints(List<astPoint> value) {
		points = value;
	}

	/**
	 * Returns the offset.
	 */
	public astPoint getOffset() {
		return offset;
	}

	/**
	 * Sets the offset to the given point.
	 * 
	 * @param offset
	 *            Point to be used for the offset.
	 */
	public void setOffset(astPoint offset) {
		this.offset = offset;
	}

	/**
	 * Returns true of the geometry is relative.
	 */
	public boolean isRelative() {
		return relative;
	}

	/**
	 * Sets the relative state of the geometry.
	 * 
	 * @param value
	 *            Boolean value to be used as the new relative state.
	 */
	public void setRelative(boolean value) {
		relative = value;
	}

	/**
	 * Swaps the x, y, width and height with the values stored in
	 * alternateBounds and puts the previous values into alternateBounds as a
	 * rectangle. This operation is carried-out in-place, that is, using the
	 * existing geometry instance. If this operation is called during a graph
	 * model transactional change, then the geometry should be cloned before
	 * calling this method and setting the geometry of the cell using
	 * mxGraphModel.setGeometry.
	 */
	public void swap() {
		if (alternateBounds != null) {
			astRectangle old = new astRectangle(getX(), getY(), getWidth(),
					getHeight());

			x = alternateBounds.getX();
			y = alternateBounds.getY();
			width = alternateBounds.getWidth();
			height = alternateBounds.getHeight();

			alternateBounds = old;
		}
	}

	/**
	 * Returns the point representing the source or target point of this edge.
	 * This is only used if the edge has no source or target vertex.
	 * 
	 * @param isSource
	 *            Boolean that specifies if the source or target point should be
	 *            returned.
	 * @return Returns the source or target point.
	 */
	public astPoint getTerminalPoint(boolean isSource) {
		return (isSource) ? sourcePoint : targetPoint;
	}

	/**
	 * Sets the sourcePoint or targetPoint to the given point and returns the
	 * new point.
	 * 
	 * @param point
	 *            Point to be used as the new source or target point.
	 * @param isSource
	 *            Boolean that specifies if the source or target point should be
	 *            set.
	 * @return Returns the new point.
	 */
	public astPoint setTerminalPoint(astPoint point, boolean isSource) {
		if (isSource) {
			sourcePoint = point;
		} else {
			targetPoint = point;
		}

		return point;
	}

	/**
	 * Translates the geometry by the specified amount. That is, x and y of the
	 * geometry, the sourcePoint, targetPoint and all elements of points are
	 * translated by the given amount. X and y are only translated if the
	 * geometry is not relative. If TRANSLATE_CONTROL_POINTS is false, then are
	 * not modified by this function.
	 * 
	 * @param dx
	 *            Integer that specifies the x-coordinate of the translation.
	 * @param dy
	 *            Integer that specifies the y-coordinate of the translation.
	 */
	public void translate(double dx, double dy) {
		// Translates the geometry
		if (!isRelative()) {
			x += dx;
			y += dy;
		}

		// Translates the source point
		if (sourcePoint != null) {
			sourcePoint.setX(sourcePoint.getX() + dx);
			sourcePoint.setY(sourcePoint.getY() + dy);
		}

		// Translates the target point
		if (targetPoint != null) {
			targetPoint.setX(targetPoint.getX() + dx);
			targetPoint.setY(targetPoint.getY() + dy);
		}

		// Translate the control points
		if (TRANSLATE_CONTROL_POINTS && points != null) {
			int count = points.size();

			for (int i = 0; i < count; i++) {
				astPoint pt = (astPoint) points.get(i);

				pt.setX(pt.getX() + dx);
				pt.setY(pt.getY() + dy);
			}
		}
	}

	/**
	 * Returns a clone of the cell.
	 */
	public Object clone() {
		astGeometry clone = (astGeometry) super.clone();

		clone.setX(getX());
		clone.setY(getY());
		clone.setWidth(getWidth());
		clone.setHeight(getHeight());
		clone.setRelative(isRelative());

		List<astPoint> pts = getPoints();

		if (pts != null) {
			clone.points = new ArrayList<astPoint>(pts.size());

			for (int i = 0; i < pts.size(); i++) {
				clone.points.add((astPoint) pts.get(i).clone());
			}
		}

		astPoint tp = getTargetPoint();

		if (tp != null) {
			clone.setTargetPoint((astPoint) tp.clone());
		}

		astPoint sp = getSourcePoint();

		if (sp != null) {
			setSourcePoint((astPoint) sp.clone());
		}

		astPoint off = getOffset();

		if (off != null) {
			clone.setOffset((astPoint) off.clone());
		}

		astRectangle alt = getAlternateBounds();

		if (alt != null) {
			setAlternateBounds((astRectangle) alt.clone());
		}

		return clone;
	}

}
